﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using System.Linq;

namespace Microsoft.Extensions.Logging.File
{
    public class FileLogger : ILogger
    {
        private static readonly string _loglevelPadding = ": ";
        private static readonly string _messagePadding;
        private static readonly string _newLineWithMessagePadding;
        private static readonly BlockingCollection<MessageContext> _messageQueue = new BlockingCollection<MessageContext>(10 * 10000);
        private static object _syncObj = new object();
        internal IExternalScopeProvider ScopeProvider { get; set; }
        internal FileLoggerOptions Options { get; set; }
        private static Task _writeTask = null;

        private readonly string _categoryName;
        static FileLogger()
        {
            string logLevelString = GetLogLevelString(LogLevel.Information);
            _messagePadding = (string)new string(' ', logLevelString.Length + _loglevelPadding.Length);
            _newLineWithMessagePadding = Environment.NewLine + _messagePadding;
        }
        public FileLogger(string categoryName)
        {
            if (categoryName == null)
            {
                throw new ArgumentNullException(nameof(categoryName));
            }
            if (_writeTask == null)
            {
                lock (typeof(FileLogger))
                {
                    if (_writeTask == null)
                    {
                        _writeTask = Task.Run(WriteWorker);
                    }
                }
            }
            this._categoryName = categoryName;
        }

        private void WriteWorker()
        {
            var count = 0;
            while (true)
            {
                try
                {
                    var messageContext = _messageQueue.Take();
                    List<string> filePaths = CalcFilePath(messageContext.Category, messageContext.Level);
                    if (filePaths != null && filePaths.Count > 0)
                    {
                        for (int i = 0; i < filePaths.Count; i++)
                        {
                            string filePath = filePaths[i].ToString();
                            try
                            {
                                if (!System.IO.File.Exists(filePath))
                                {
                                    Directory.CreateDirectory(new FileInfo(filePath).Directory.FullName);
                                }
                                System.IO.File.AppendAllLines(filePath, new List<string>() { messageContext.Message });
                            }
                            catch (Exception ex)
                            {
                                LogInternalError(ex);
                            }
                        }
                    }
                    //自动净化日志目录
                    count++;
                    if (Options.AutoClear != null
                        && Options.AutoClear.Enable.Value
                        && Options.AutoClear.ExpireSpan > 0
                        && Options.AutoClear.TriggerCount > 0
                        && Options.AutoClear.Dirs != null && Options.AutoClear.Dirs.Count > 0
                        && Options.AutoClear.Exts != null && Options.AutoClear.Exts.Count > 0)
                    {
                        if (count % Options.AutoClear.TriggerCount == 0)
                        {
                            //执行净化
                            var files = new List<string>();
                            Options.AutoClear.Exts.ForEach(ext =>
                            {
                                Options.AutoClear.Dirs.ForEach(dir => files.AddRange(dir.GetRecurseFiles(ext)));
                            });
                            files = files.Distinct().ToList();
                            foreach (var file in files)
                            {
                                if (DateTime.Now - System.IO.File.GetCreationTime(file) > TimeSpan.FromSeconds(Options.AutoClear.ExpireSpan.Value))
                                {
                                    try { System.IO.File.Delete(file); } catch { }
                                }
                            }
                            count = 1;
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogInternalError(ex);
                }
            }
        }

        /// <summary>
        /// 内部输出日志
        /// </summary>
        /// <param name="level"></param>
        /// <param name="msg"></param>
        private void LogInternal(LogLevel level, string msg, string detail = null)
        {
            if (!string.IsNullOrWhiteSpace(Options.InternalLogFile))
            {
                var path = _generateLogPath(Options.InternalLogFile, LogLevel.Debug, null);
                var str = $@"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] [{GetLogLevelString(level)}] {msg}";
                if (!string.IsNullOrWhiteSpace(detail))
                {
                    str += $"{_newLineWithMessagePadding}{detail}";
                }
                lock (_syncObj)
                {
                    System.IO.File.AppendAllLines(path, new string[] { str });
                }
            }
        }

        /// <summary>
        /// 内部输出日志
        /// </summary>
        /// <param name="level"></param>
        /// <param name="msg"></param>
        private void LogInternalError(Exception ex)
        {
            LogInternal(LogLevel.Error, ex.Message, ex.StackTrace);
        }

        public IDisposable BeginScope<TState>(TState state)
        {
            if (state == null)
            {
                return this.ScopeProvider.Push(NullScope.Instance);
            }
            else
            {
                return this.ScopeProvider.Push(state);
            }
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return logLevel != LogLevel.None;
        }

        private string GetScopeInformation()
        {
            StringBuilder sb = new StringBuilder();
            IExternalScopeProvider scopeProvider = this.ScopeProvider;
            if (this.Options.IncludeScopes.Value && (scopeProvider != null))
            {
                scopeProvider.ForEachScope<StringBuilder>(delegate (object scope, StringBuilder stringBuilder)
                {
                    stringBuilder.Append(_messagePadding + "=> ");
                    stringBuilder.Append(scope);
                }, sb);
                if (sb.Length > 0)
                {
                    sb.AppendLine();
                }
            }
            return sb.ToString();
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (this.IsEnabled(logLevel))
            {
                if (formatter == null)
                {
                    LogInternal(LogLevel.Error, "指定的日志格式化器不能为null!");
                    throw new ArgumentNullException(nameof(formatter));
                }
                string str = formatter(state, exception);
                if (!string.IsNullOrEmpty(str) || (exception != null))
                {
                    string lev = GetLogLevelString(logLevel);
                    string msg = $@"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}]-[{lev}]: {_categoryName}[{eventId}]-[threed:{Thread.CurrentThread.ManagedThreadId}]" + Environment.NewLine;
                    string scopemsg = GetScopeInformation();
                    if (!string.IsNullOrWhiteSpace(scopemsg))
                    {
                        msg += scopemsg;
                    }
                    var tmsg = str.Replace(Environment.NewLine, Environment.NewLine + INDENT).Trim(' ');
                    msg += INDENT + tmsg;
                    if (exception != null)
                    {
                        var _tmp = exception.Message + Environment.NewLine + exception.StackTrace;
                        msg += _tmp.Replace(Environment.NewLine, Environment.NewLine + INDENT);
                    }
                    this.WriteMessage(logLevel, msg);
                }
            }
        }

        public const string INDENT = "     ";


        public void WriteMessage(LogLevel logLevel, string msg)
        {
            _messageQueue.Add(new MessageContext()
            {
                Category = _categoryName,
                Level = logLevel,
                Message = msg
            });
        }


        private List<string> CalcFilePath(string category, LogLevel logLevel)
        {
            List<string> filePaths = new List<string>();
            if (Options.Writers != null)
            {
                foreach (var writer in Options.Writers)
                {
                    //先观察日志级别是否满足
                    if (writer.MinLogLevel > logLevel || writer.MaxLogLevel < logLevel) continue;

                    /* 算法：
                     * 首先假设writer不输出这个日志类别,然后FirstInclude包含的话就输出,最后SecondExclude包含的话就移除,最后得到的结果就是是否输出
                     */
                    if (writer.FirstInclude != null && writer.FirstInclude.Count > 0 && writer.Path != null)
                    {
                        var b = false;
                        foreach (var include in writer.FirstInclude)
                        {
                            if (include == "*")
                            {
                                b = true;
                                break;
                            }
                            if (include.Contains("*"))
                            {
                                var arr = include.Split(new char[] { '*' }, StringSplitOptions.RemoveEmptyEntries);
                                if (arr.Length > 2)
                                {
                                    throw new Exception($"日志类别配置出错: {include}");
                                }
                                if (category.StartsWith(arr[0]) || (arr.Length == 2 && category.EndsWith(arr[1])))
                                {
                                    b = true;
                                    break;
                                }
                            }
                            else
                            {
                                if (category.StartsWith(include))
                                {
                                    b = true;
                                    break;
                                }
                            }
                        }
                        if (b && writer.SecondExclude != null && writer.SecondExclude.Count > 0)
                        {
                            foreach (var exclude in writer.SecondExclude)
                            {
                                if ((exclude == "*"))
                                {
                                    b = false;
                                    break;
                                }
                                if (exclude.Contains("*"))
                                {
                                    var arr = exclude.Split(new char[] { '*' }, StringSplitOptions.RemoveEmptyEntries);
                                    if (arr.Length > 2)
                                    {
                                        throw new Exception($"日志类别配置出错: {exclude}");
                                    }
                                    if (category.StartsWith(arr[0]) || (arr.Length == 2 && category.EndsWith(arr[1])))
                                    {
                                        b = false;
                                        break;
                                    }
                                }
                                else
                                {
                                    if (category.StartsWith(exclude))
                                    {
                                        b = false;
                                        break;
                                    }
                                }
                            }
                        }
                        if (b)
                        {
                            filePaths.Add(_generateLogPath(writer.Path, logLevel, writer));
                        }
                    }
                }
            }
            return filePaths;
        }

        static ConcurrentDictionary<string, string> _rollingFiles = new ConcurrentDictionary<string, string>();
        static ConcurrentDictionary<string, int> _rollingCunter = new ConcurrentDictionary<string, int>();
        private string _generateLogPath(string path, LogLevel logLevel, Writer writer)
        {
            Regex reg = new Regex(@".{0,}#datetime\{(.+)\}#.{0,}");
            if (reg.IsMatch(path))
            {
                Match mat = reg.Match(path);
                if (mat.Success && mat.Groups.Count == 2)
                {
                    string datetimeStr = DateTime.Now.ToString(mat.Groups[1].Value);
                    path = path.Replace("#datetime{" + mat.Groups[1].Value + "}#", datetimeStr);
                }
            }
            var level = GetLogLevelString(logLevel);
            path = path.Replace("#level#", level);
            path = Path.Combine(Options.BaseDirectory, path);
            path = _generateRollingPath(path, writer);
            return path;
        }

        private string _generateRollingPath(string path, Writer writer)
        {
            if (writer == null) return path;
            if (path == null) return path;
            if (!System.IO.File.Exists(path)) return path;
            var file = new FileInfo(path);
            var filePrefix = Path.GetFileNameWithoutExtension(path);
            var files = Directory.GetFiles(file.DirectoryName, filePrefix + "-r*" + file.Extension);
            files.Select(i => Path.GetFileNameWithoutExtension(i));
            var order = files.ToList()
                .Select(i => i.GetOrderStr())
                .Where(i => !string.IsNullOrWhiteSpace(i))
                .Select(i => int.Parse(i))
                .OrderByDescending(i => i)
                .FirstOrDefault();
            if (order > 0)
            {
                //已经滚动过
                file = new FileInfo(Path.Combine(file.DirectoryName, filePrefix + "-r" + order + file.Extension));
                if (file.Length > writer.RollingSize)
                {
                    order++;
                }
                path = Path.Combine(file.DirectoryName, filePrefix + "-r" + order + file.Extension);
            }
            else
            {
                //尚未发生滚动
                file = new FileInfo(path);
                if (System.IO.File.Exists(path) && file.Length > writer.RollingSize)
                {
                    path = Path.Combine(file.DirectoryName, filePrefix + "-r1" + file.Extension);
                }
            }
            return path;
        }

        private static string GetLogLevelString(LogLevel logLevel)
        {
            switch (logLevel)
            {
                case LogLevel.Trace:
                    return "trce";
                case LogLevel.Debug:
                    return "dbug";
                case LogLevel.Information:
                    return "info";
                case LogLevel.Warning:
                    return "warn";
                case LogLevel.Error:
                    return "fail";
                case LogLevel.Critical:
                    return "crit";
                default:
                    return "none";
            }
        }
    }

    internal class MessageContext
    {
        /// <summary>
        /// 日志等级
        /// </summary>
        public LogLevel Level { get; set; }

        /// <summary>
        /// 日志类别
        /// </summary>
        public string Category { get; set; }

        /// <summary>
        /// 消息内容
        /// </summary>
        public string Message { get; set; }
    }

    internal static class StringExtensions
    {
        public static string GetOrderStr(this string path)
        {
            var name = Path.GetFileNameWithoutExtension(path);
            if (name.Contains("-r"))
            {
                var orderStr = name.Substring(name.LastIndexOf("-r") + 2);
                return orderStr;
            }
            else
            {
                return "";
            }
        }
    }

    internal static class DirectoryExtensions
    {
        public static List<string> GetRecurseFiles(this string dir, string pattern)
        {
            var res = new List<string>();
            _fileFiles(dir, pattern, res);
            return res.Distinct().ToList();

            void _fileFiles(string _dir, string _pattern, List<string> files)
            {
                if (Directory.Exists(_dir))
                {
                    var dirs = Directory.GetDirectories(_dir);
                    foreach (var item in dirs)
                    {
                        _fileFiles(item, pattern, files);
                    }
                    var _files = Directory.GetFiles(_dir, pattern);
                    files.AddRange(_files);
                }
            }
        }
    }
}
